﻿//-----------------------------------------------------------------------
// <copyright file="AccountRepository.cs" company="FastNET Services">
//     Copyright (c) 2008-2009. All Rights Reserved.
// </copyright>
// <license>
//     GPLv2
// </license>
// <warranty>
//     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//     BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
//     DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </warranty>
//-----------------------------------------------------------------------
#region Usings

using System;
using System.Collections.Generic;
using System.Data.EntityClient;
using System.Linq;
using GateShift.Common.Repository;
using GateShift.Common.Repository.Interfaces.DataInterfaces;
using GateShift.Common.Repository.Interfaces.Repositories;
using GateShift.Services.GameData.Entities;

#endregion

namespace GateShift.Services.GameData.Repositories
{
    /// <summary>
    /// General repository for managing Accounts
    /// </summary>
    internal class AccountRepository : IAccountRepository
    {
        #region CTor/DTor

        /// <summary>
        /// Initializes a new instance of the AccountRepository class
        /// </summary>
        /// <param name="connection">Reference to the common EntityConnection for the repository</param>
        internal AccountRepository(EntityConnection connection)
        {
            Connection = connection;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets a reference to the common EntityConnection for the repository
        /// </summary>
        protected EntityConnection Connection { get; private set; }

        #endregion

        #region IAccountRepository Members

        /// <summary>
        /// Retrieves a single IAccountData by looking at the AccountName
        /// </summary>
        /// <param name="AccountName">Account name to search by</param>
        /// <returns>IAccountData found or null if no account was found</returns>
        /// <exception cref="ArgumentNullException">Thrown if the search value is null or empty</exception>
        public IAccountData RetrieveByUserName(string AccountName)
        {
            if (string.IsNullOrEmpty(AccountName))
            {
                throw new ArgumentNullException("AccountName", "An account name must be provided and cannot be empty");
            }

            using (EntityDataAdapter dataSource = new EntityDataAdapter(Connection))
            {
                Account foundAccount = (from
                                               account
                                           in
                                            dataSource.Accounts
                                        where
                                            account.AccountName == AccountName
                                        select
                                            account).FirstOrDefault();

                if (foundAccount != null)
                {
                    dataSource.Detach(foundAccount);
                }

                return foundAccount;
            }
        }

        /// <summary>
        /// Retrieves a single IAccountData by looking at the EmailAddress
        /// </summary>
        /// <param name="EmailAddress">Email Address to search by</param>
        /// <returns>IAccountData found or null if no account was found</returns>
        /// <exception cref="ArgumentNullException">Thrown if the search value is null or empty</exception>
        public IAccountData RetrieveByEmailAddress(string EmailAddress)
        {
            if (string.IsNullOrEmpty(EmailAddress))
            {
                throw new ArgumentNullException("EmailAddress", "An email address must be provided and cannot be empty");
            }

            using (EntityDataAdapter dataSource = new EntityDataAdapter(Connection))
            {
                Account foundAccount = (from
                                               account
                                           in
                                            dataSource.Accounts
                                        where
                                            account.EmailAddress == EmailAddress
                                        select
                                            account).FirstOrDefault();

                if (foundAccount != null)
                {
                    dataSource.Detach(foundAccount);
                }

                return foundAccount;
            }
        }

        /// <summary>
        /// Checks to see if an IAccountData exists by the AccountName
        /// </summary>
        /// <param name="AccountName">Account name to search by</param>
        /// <param name="IgnoreID">ID of an account to ignore during check</param>
        /// <returns>Boolean whether or not an account exists in the system</returns>
        /// <exception cref="ArgumentNullException">Thrown if AccountName is null or empty</exception>
        /// <exception cref="InvalidCastException">Thrown if IgnoreID is of the wrong type for this repository</exception>
        public bool AccountNameExists(string AccountName, Identity? IgnoreID)
        {
            if (string.IsNullOrEmpty(AccountName))
            {
                throw new ArgumentNullException("AccountName", "Must have an account name to search by");
            }

            using (EntityDataAdapter dataSource = new EntityDataAdapter(Connection))
            {
                if (IgnoreID.HasValue)
                {
                    if (!IgnoreID.Value.CanFitInGuid())
                    {
                        throw new InvalidCastException("IgnoreID must be of type Guid for this repository");
                    }

                    Guid searchID = IgnoreID.Value;
                    return (from
                                    account
                                in
                                dataSource.Accounts
                            where
                                account.AccountName == AccountName &&
                                account.AccountID != searchID
                            select
                                account).Count() > 0;
                }

                return (from
                                account
                            in
                            dataSource.Accounts
                        where
                            account.AccountName == AccountName
                        select
                            account).Count() > 0;
            }
        }

        /// <summary>
        /// Checks to see if an IAccountData exists by the EmailAddress
        /// </summary>
        /// <param name="EmailAddress">Email Address to search by</param>
        /// <param name="IgnoreID">ID of an account to ignore during check</param>
        /// <returns>Boolean whether or not an account exists in the system</returns>
        /// <exception cref="ArgumentNullException">Thrown if EmailAddress is null or empty</exception>
        /// <exception cref="InvalidCastException">Thrown if IgnoreID is of the wrong type for this repository</exception>
        public bool EmailAddressExists(string EmailAddress, Identity? IgnoreID)
        {
            if (string.IsNullOrEmpty(EmailAddress))
            {
                throw new ArgumentNullException("EmailAddress", "Must have an email address to search by");
            }

            using (EntityDataAdapter dataSource = new EntityDataAdapter(Connection))
            {
                if (IgnoreID.HasValue)
                {
                    if (!IgnoreID.Value.CanFitInGuid())
                    {
                        throw new InvalidCastException("IgnoreID must be of type Guid for this repository");
                    }

                    Guid searchID = IgnoreID.Value;
                    return (from
                                    account
                                in
                                dataSource.Accounts
                            where
                                account.EmailAddress == EmailAddress &&
                                account.AccountID != searchID
                            select
                                account).Count() > 0;
                }

                return (from
                                account
                                in
                            dataSource.Accounts
                        where
                            account.EmailAddress == EmailAddress
                        select
                            account).Count() > 0;
            }
        }

        #endregion

        #region IRepository<IAccountData> Members

        /// <summary>
        /// Retrieves all instances of IAccountData from the repository
        /// </summary>
        /// <returns>IEnumerable of the requested IData</returns>
        public IList<IAccountData> Retrieve()
        {
            List<IAccountData> returnValues = new List<IAccountData>();

            using (EntityDataAdapter dataSource = new EntityDataAdapter(Connection))
            {
                IEnumerable<Account> query = from account in dataSource.Accounts select account;

                foreach (Account account in query)
                {
                    returnValues.Add(account);
                    dataSource.Detach(account);
                }
            }

            return returnValues;
        }

        /// <summary>
        /// Retrieves the requested IAccountData by an Identity
        /// </summary>
        /// <param name="ID">Identity to retrieve the data by</param>
        /// <returns>IData instance or null if one was not found</returns>
        /// <exception cref="InvalidCastException">Thrown if the identity is of the wrong type for this datasource</exception>
        public IAccountData Retrieve(Identity ID)
        {
            if (!ID.CanFitInGuid())
            {
                throw new InvalidCastException(string.Format("Identity for this type of repository requires it to be of Guid Type. Currently is {0}.", ID.UnderlyingType()));
            }

            // This has to be done here because LINQ does not support the inline case
            Guid searchID = ID;

            using (EntityDataAdapter dataSource = new EntityDataAdapter(Connection))
            {
                Account foundAccount = (from
                                               account
                                               in
                                            dataSource.Accounts
                                        where
                                            account.AccountID == searchID
                                        select
                                            account).FirstOrDefault();

                if (foundAccount != null)
                {
                    dataSource.Detach(foundAccount);
                }

                return foundAccount;
            }
        }

        /// <summary>
        /// Deletes the requested IAccountData
        /// </summary>
        /// <param name="Data">IAccountData to delete from the repository</param>
        /// <exception cref="ArgumentNullException">Thrown if data is null</exception>
        /// <exception cref="InvalidCastException">Thrown if the data is of the wrong type for this datasource</exception>
        /// <exception cref="InvalidOperationException">Thrown if the IAccountData is new or already deleted</exception>
        public void Delete(IAccountData Data)
        {
            if (Data == null)
            {
                throw new ArgumentNullException("Data", "Must have a valid Data to delete");
            }

            if (!(Data is Account))
            {
                throw new InvalidCastException("Data is not of type Account. Cannot delete this object through this repository");
            }

            if (Data.IsNew)
            {
                throw new InvalidOperationException("Data cannot be new before deleting. There is nothing to delete.");
            }

            if (Data.IsDeleted)
            {
                throw new InvalidOperationException("Data already deleted.");
            }

            using (EntityDataAdapter dataSource = new EntityDataAdapter(Connection))
            {
                dataSource.Attach((Account)Data);
                dataSource.DeleteObject(Data);
                dataSource.SaveChanges();
            }
        }

        /// <summary>
        /// Saves the requested IAccountData
        /// </summary>
        /// <param name="Data">IAccountData to save to the repository</param>
        /// <exception cref="ArgumentNullException">Thrown if data is null</exception>
        /// <exception cref="InvalidCastException">Thrown if the data is of the wrong type for this datasource</exception>
        public void Save(IAccountData Data)
        {
            if (Data == null)
            {
                throw new ArgumentNullException("Data", "Must have a valid Data to delete");
            }

            if (!(Data is Account))
            {
                throw new InvalidCastException("Data is not of type Account. Cannot save this object through this repository");
            }

            if (!Data.IsDirty)
            {
                return;
            }

            using (EntityDataAdapter dataSource = new EntityDataAdapter(Connection))
            {
                if (Data.IsNew)
                {
                    dataSource.AddToAccounts((Account)Data);
                    dataSource.SaveChanges();
                    dataSource.Detach(Data);
                }
                else
                {
                    dataSource.ApplyPropertyChanges("Accounts", Data);
                    dataSource.SaveChanges();
                }
            }
        }

        /// <summary>
        /// Creates a new instance of IAccountData
        /// </summary>
        /// <returns>New Instance of IAccountData</returns>
        public IAccountData Create()
        {
            return new Account();
        }

        #endregion
    }
}